package com.cougil.uoc.seghismed.business.impl;

import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import com.cougil.uoc.seghismed.business.AppService;
import com.cougil.uoc.seghismed.business.CipherService;
import com.cougil.uoc.seghismed.business.MetgeService;
import com.cougil.uoc.seghismed.business.PacientService;
import com.cougil.uoc.seghismed.model.Usuari;
import com.cougil.uoc.seghismed.model.ciphed.UsuariCiphed;
import com.cougil.uoc.seghismed.model.comunicacions.Peticio;
import com.cougil.uoc.seghismed.model.comunicacions.Resposta;
import com.cougil.uoc.seghismed.model.comunicacions.ciphed.PeticioCiphed;
import com.cougil.uoc.seghismed.model.comunicacions.ciphed.RespostaCiphed;
import com.cougil.uoc.seghismed.model.comunicacions.enums.Operacio;
import com.cougil.uoc.seghismed.model.comunicacions.enums.Resultat;
import com.cougil.uoc.seghismed.utils.SeghismedException;
import com.cougil.uoc.seghismed.utils.SeghismedUtils;

/**
 * Classe principal que conté la lògica dels mètodes principals comuns a tots els serveis web
 *
 * @author Ignacio Cougil Jares
 */

public class AppServiceImpl implements AppService {

	private static final Logger logger = Logger.getLogger(AppServiceImpl.class);

	// Serveis de suport emprats
	private CipherService cipherService;

	// Usuari gestor
	private Usuari gestor;

	private MetgeService metgeService;

	private PacientService pacientService;

	// mapa encarregat d'emmagatzemar els usuaris més les peticions associades
	// que disposen dels valors aleatoris: ng, ni, etc
	private Map<Usuari, Peticio> usuarisAutenticats = new HashMap<Usuari, Peticio>();

	/* (non-Javadoc)
	 * @see com.cougil.uoc.seghismed.business.AppService#autenticar(com.cougil.uoc.seghismed.model.comunicacions.ciphed.PeticioCiphed)
	 */
	@Override
	public RespostaCiphed autenticar(PeticioCiphed pCiphed) {

		// SeghismedUtils.showClassAbsolutePath(this.getClass());

		// AppServiceImpl.logger.debug("Petició xifrada rebuda: [" + pCiphed + "]");

		// desxifrem la petició

		Peticio p = this.cipherService.desxifrarPeticio(pCiphed, this.findGestor());

		Usuari uOrigen = this.findUsuari(p.getUsuariOrigen());
		Usuari uDesti = this.findUsuari(p.getUsuariDesti());

		Operacio opActual = p.getOperacio();

		AppServiceImpl.logger.debug("Autenticant operació: [" + opActual + "]");

		Resposta r = this.peticioToResposta(p, uOrigen, uDesti);

		if (this.checkPeticio(p, uOrigen, uDesti, r)) {

			AppServiceImpl.logger.info("Operació autenticada! ["
					+ p.getOperacio() + "]");

			// Pas previ al protocol d'autenticació de Needham-Schroeder: comprovem si l'usuari ja
			// s'havia autenticat anteriorment
			if (this.isAutenticat(uOrigen)) {
				// tot OK!
				AppServiceImpl.logger.info("Usuari [" + uOrigen + "] ja autenticat previament!!");
				r.setOperacio(Operacio.Autenticat);
				r.setResultat(Resultat.OK);
			} else {
				// 1er pas de l'autenticació (al client): generar un num. aleatori i enviar-lo al servidor

				// 2on pas de l'autenticació: cal generar un altre num. aleatori i retornar-lo al client
				if (Operacio.Autenticar_Gestor_Desxifrar_Ni_IdUsuari.equals(opActual)) {
					// generem un número aleatori i guardem la seva relació
					p.setNg(SeghismedUtils.getRandomInt());
					this.usuarisAutenticats.put(uOrigen, p);
					r.setNg(p.getNg());
					r.setResultat(Resultat.OK);
					AppServiceImpl.logger.info("Ni verificat!!");

				// 3er pas de l'autenticació (al client): verificar que ni i ni'(xifrat pel servidor) són iguals


				// 4rt pas de l'autenticació: verificar que ng i ng' (xifrat pel client) són iguals
				} else if (Operacio.Autenticar_Gestor_Desxifrar_Ng.equals(opActual)) {
					p.setOperacio(Operacio.Autenticat);
					this.usuarisAutenticats.put(uOrigen, p);
					r.setResultat(Resultat.OK);
					r.setOperacio(Operacio.Autenticat);
					AppServiceImpl.logger.info("Usuari [" + uOrigen + "] autenticat!!");

				} else if (Operacio.Autenticar_Gestor_Esborrar_Relacio.equals(opActual)) {
					// eliminem l'usuari d'entre els autenticats, doncs alguna cosa ha anat malament
					AppServiceImpl.logger.info("Esborrem l'usuari [" + uOrigen + "] !!");
					this.usuarisAutenticats.remove(uOrigen);

				} else {
					AppServiceImpl.logger.error("Error autenticant usuari!!. Peticio: [" + p + "]");
					// eliminem l'usuari d'entre els autenticats, doncs alguna cosa ha anat malament
					this.usuarisAutenticats.remove(uOrigen);
				}
			}

		} else {
			// eliminem l'usuari d'entre els autenticats, doncs alguna cosa ha anat malament
			AppServiceImpl.logger.info("Esborrem l'usuari ["+uOrigen+"] !!");
			this.usuarisAutenticats.remove(uOrigen);
		}

		AppServiceImpl.logger.debug("Resposta a enviar: [" + r + "]");

		return this.cipherService.xifrarResposta(r, uDesti, uOrigen);
	}

	/**
	 * Verifica si la petició actual és correcta, respecte l'anterior petició rebuda pel
	 * mateix client. Entre d'altres verifica si l'ordre és correcte, si l'usuari existia
	 * previament o no i atenent a la petició que es remet, verifica que els valors
	 * aleatoris són els esperats
	 *
	 * @param p
	 *            {@link Peticio} a verificar
	 * @param uOrigen
	 *            {@link Usuari} usuari origen de la petició
	 * @param uDesti
	 *            {@link Usuari} usuari destí de la petició
	 * @param r
	 *            {@link Resposta} a retornar
	 * @return booleà que indica si és correcte o no
	 */
	private boolean checkPeticio(Peticio p, Usuari uOrigen, Usuari uDesti, Resposta r) {
		boolean isOk = false;

		// si s'han trobat tots dos usuaris
		if (uOrigen != null && uDesti != null) {

			Operacio opActual = p.getOperacio();

			if (opActual != null) {

				Peticio pAnterior = this.usuarisAutenticats.get(uOrigen);

				// depenent de la petició anterior comprovem la petició actual
				if (pAnterior != null) {

					Operacio opAnterior = pAnterior.getOperacio();

					if (Operacio.Autenticar_Gestor_Desxifrar_Ni_IdUsuari.equals(opAnterior) &&
							Operacio.Autenticar_Gestor_Desxifrar_Ng.equals(opActual)) {
						// 4ª part de l'autenticació

						// coincideixen els Ng (client i servidor)
						if (pAnterior.getNg().equals(p.getNg())) {
							isOk = true;
						} else {
							r.setResultat(Resultat.KO);
							String descr = SeghismedUtils.getProperty("gestor.op.protocol.token.err.expected")+" "+
							pAnterior.getNg()+" - "+SeghismedUtils.getProperty("gestor.op.protocol.token.err.received")+" "+
							p.getNg();
							r.setDescripcio(descr);
							AppServiceImpl.logger.debug(descr);
						}

					/*
						 * tant si s'acaba d'autenticar el client, com es preten esborrar o bé ja estava
						 * autenticat, la petició és correcta
						 */
					} else if (Operacio.Autenticar_Gestor_Esborrar_Relacio.equals(opActual)
							|| Operacio.Autenticat.equals(opActual)
							|| Operacio.Autenticat.equals(opAnterior)) {
						isOk = true;

					} else if (Operacio.Autenticar_Gestor_Esborrar_Relacio.equals(opActual) ||
							Operacio.Autenticat.equals(opActual)) {
						isOk = true;

					} else {
						// en cap dels altres casos, la petició és errónia
						r.setResultat(Resultat.KO);
						String descr = SeghismedUtils.getProperty("gestor.op.protocol.order.err")+" "+opActual.getNom();
						r.setDescripcio(descr);
						AppServiceImpl.logger.debug(descr);
					}

					// no existeix cap petició anterior
				} else {

					if (Operacio.Autenticar_Gestor_Desxifrar_Ni_IdUsuari.equals(opActual) ||
							Operacio.Autenticar_Gestor_Esborrar_Relacio.equals(opActual)) {
						// OK - no hi havia petició prèvia però l'actual és la 1ª
						// part de l'autenticació o bé
						//		hi ha hagut un error
						isOk = true;

					} else {
						r.setResultat(Resultat.KO);
						String descr = SeghismedUtils.getProperty("gestor.op.protocol.order.err")+" "+opActual.getNom();
						r.setDescripcio(descr);
						AppServiceImpl.logger.debug(descr);
					}

				}

			} else {
				r.setResultat(Resultat.KO);
				String descr = SeghismedUtils.getProperty("gestor.op.unknown")+" "+opActual;
				r.setDescripcio(descr);
				AppServiceImpl.logger.debug(descr);
			}

		} else {
			r.setResultat(Resultat.KO);
			StringBuffer descr = new StringBuffer();
			if (uOrigen == null) {
				descr = descr.append(SeghismedUtils.getProperty("gestor.op.user.sender.unkown"));
			}
			if (uDesti == null) {
				descr = descr.append(SeghismedUtils.getProperty("gestor.op.user.receiver.unkown"));
			}
			r.setDescripcio(descr.toString());
			AppServiceImpl.logger.error(descr.toString());
		}

		return isOk;
	}

	@Override
	public Usuari findGestor() {
		//DNI: 00000000-A - Gestor - gestor@seghismed.uoc.edu - OrganizationalUnit: Gestio
		if (this.gestor == null) {
			this.gestor = SeghismedUtils.getUsuariGestor(this.cipherService);
		}
		return this.gestor;
	}

	private Usuari findUsuari(Usuari usuari) {
		Usuari u = null;
		if (SeghismedUtils.isMetge(usuari)){
			u = this.metgeService.findByDni(usuari.getDni());
		} else if (SeghismedUtils.isPacient(usuari)){
			u = this.pacientService.findByDni(usuari.getDni());
		} else if (SeghismedUtils.isGestor(usuari)){
			/*
			 * Donat que el certificat del Gestor no està guardat a BBDD, sinó que o bé
			 * està en un fitxer .crt accesible per l'aplicació Client o bé en un
			 * contenidor PKCS#12 (arxiu .P12) disponible a l'aplicació servidora,
			 * depenent del tipus d'aplicació/actor, cercarem el certificat públic del
			 * Gestor d'una manera o d'una altra
			 */
			u = this.findGestor();
		} else {
			throw new SeghismedException("Usuari ["+usuari+"] no reconegut!");
		}
		return u;
	}

	/**
	 * Retorna si l'usuari informat està autenticat o no, és a dir, es troba dintre del
	 * mapa que conté els usuaris autenticats
	 *
	 * @param key
	 *            {@link Usuari} usuari a comprovar si està autenticat o no
	 * @return boolèa que indica si es troba o no l'usuari autenticat
	 * @see java.util.Map#containsKey(java.lang.Object)
	 */
	public boolean isAutenticat(Usuari u) {
		// els usuaris no es troben si intentem comparar tots els seus atributs (veure métode equals
		// de Usuari/Pacient/Metge)
		Peticio pTrobada = this.usuarisAutenticats.get(u);
		boolean autenticat = false;
		if (pTrobada != null) {
			autenticat = Operacio.Autenticat.equals( pTrobada.getOperacio() );
		}
		return autenticat;
	}

	/* (non-Javadoc)
	 * @see com.cougil.uoc.seghismed.business.AppService#isAutenticat(com.cougil.uoc.seghismed.model.ciphed.UsuariCiphed)
	 */
	@Override
	public boolean isAutenticat(UsuariCiphed uCiphed) {
		Usuari u = this.cipherService.desxifrarUsuari(uCiphed, this.findGestor());
		return this.isAutenticat(u);
	}



	private Resposta peticioToResposta(Peticio p, Usuari uOrigen, Usuari uDesti) {
		Resposta r = new Resposta();
		r.setNg( p.getNg() );
		r.setNi( p.getNi() );
		// intercanviem els usuaris
		r.setUsuariOrigen(uDesti);
		r.setUsuariDesti(uOrigen);
		r.setResultat(Resultat.KO);
		// informem per defecte el mateix nom de l'operació a la resposta
		r.setOperacio(p.getOperacio());
		return r;
	}

	/* (non-Javadoc)
	 * @see com.cougil.uoc.seghismed.business.AppService#send(com.cougil.uoc.seghismed.model.Usuari, com.cougil.uoc.seghismed.model.Usuari, com.cougil.uoc.seghismed.model.comunicacions.Peticio)
	 */
	public Resposta sendAutenticar(Usuari emisor, Usuari destinatari, Peticio peticio) {
		// xifrem la petició
		PeticioCiphed peticioCiphed = this.cipherService.xifrarPeticio(peticio, emisor, destinatari);

		// l'enviem al servidor
		RespostaCiphed respostaCiphed = this.autenticar(peticioCiphed);

		// desxifrem el resultat
		return this.cipherService.desxifrarResposta(respostaCiphed,emisor);
	}

	/**
	 * @param Estableix cipherService com a valor de la propietat cipherService
	 */
	public void setCipherService(CipherService cipherService) {
		this.cipherService = cipherService;
	}

	/**
	 * @param Estableix
	 *            metgeService com a valor de la propietat metgeService
	 */
	public void setMetgeService(MetgeService metgeService) {
		this.metgeService = metgeService;
	}

	/**
	 * @param Estableix
	 *            pacientService com a valor de la propietat pacientService
	 */
	public void setPacientService(PacientService pacientService) {
		this.pacientService = pacientService;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.cougil.uoc.seghismed.business.AppService#sortir(com.cougil.uoc.seghismed.model.comunicacions.ciphed.PeticioCiphed)
	 */
	public RespostaCiphed sortir (PeticioCiphed pCiphed) {
		Resposta r = new Resposta();

		// desxifrem la petició
		Peticio p = this.cipherService.desxifrarPeticio(pCiphed, this.findGestor());

		Usuari uOrigen = this.findUsuari(p.getUsuariOrigen());
		Usuari uDesti = this.findUsuari(p.getUsuariDesti());

		// esborrem l'usuari
		this.usuarisAutenticats.remove(uOrigen);
		r.setResultat(Resultat.OK);

		AppServiceImpl.logger
		.info("Usuari " + uOrigen + " abandona la sessió!");

		// retornem la resposta xifrada
		return this.cipherService.xifrarResposta(r, uDesti, uOrigen);
	}



}
